package studio.xiaoyun.web;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.fasterxml.jackson.databind.ObjectMapper;

import studio.xiaoyun.core.exception.CoreException;
import studio.xiaoyun.core.exception.ErrorCode;
import studio.xiaoyun.core.parameter.Parameter;
import studio.xiaoyun.core.parameter.criterion.Criterion;
import studio.xiaoyun.core.parameter.criterion.Query;

/**
 * 辅助类，方便从HTTP请求中获得参数
 * @author 岳正灵
 * @since 1.0.0
 */
public class ParameterUtil {
	
	/**
	 * 从请求中获得Date类型的参数。
	 * <p>参数的格式应该是yyyy-MM-dd
	 * @param request HTTP请求
	 * @param parameterName 参数名
	 * @return 参数的值，如果参数不存在，则返回null
	 * @throws CoreException 如果参数的值不是Date类型，则抛出异常
	 */
	public static Date getDate(HttpServletRequest request,String parameterName)throws CoreException{
		return getDate(request,parameterName,"yyyy-MM-dd");
	}
	
	/**
	 * 从请求中获得Date类型的参数
	 * @param request HTTP请求
	 * @param parameterName 参数名
	 * @param pattern 日期格式, 例如:yyyy-MM-dd
	 * @return 参数的值，如果参数不存在，则返回null
	 * @throws CoreException 如果参数的值不是Date类型，则抛出异常
	 */
	public static Date getDate(HttpServletRequest request,String parameterName,String pattern)throws CoreException{
		Date date = null;
		String value = request.getParameter(parameterName);
		if(value!=null){
			try{
				date = new SimpleDateFormat(pattern).parse(value);
			}catch(Exception e){
				throw new CoreException(ErrorCode.PARAMETER_ERROR,"参数"+parameterName+"的格式错误:"+value);
			}
		}
		return date;
	}
	
	/**
	 * 从请求中获得Double类型的参数
	 * @param request HTTP请求
	 * @param parameterName 参数名
	 * @return 参数的值，如果参数不存在，则返回null
	 * @throws CoreException 如果参数的值不是Double类型，则抛出异常
	 */
	public static Double getDouble(HttpServletRequest request,String parameterName)throws CoreException{
		return getDouble(request,parameterName,null);
	}
	
	/**
	 * 从请求中获得Double类型的参数
	 * @param request HTTP请求
	 * @param parameterName 参数名
	 * @param defaultValue 默认值
	 * @return 参数的值，如果参数不存在，则返回默认值
	 * @throws CoreException 如果参数的值不是Double类型，则抛出异常
	 */
	public static Double getDouble(HttpServletRequest request,String parameterName,Double defaultValue)throws CoreException{
		Double result = defaultValue;
		String value = request.getParameter(parameterName);
		if(value!=null){
			try{
				result = Double.valueOf(value.trim());
			}catch(NumberFormatException e){
				throw new CoreException(ErrorCode.PARAMETER_ERROR,parameterName+"参数应该是数字:"+value);
			}
		}
		return result;
	}
	
	/**
	 * 从HTTP请求中取得{@value studio.xiaoyun.web.PublicParameter#FIELD}的值。
	 * <p>多个值之间以逗号分隔
	 * @param request HTTP请求
	 * @return 返回结果中应该包括的字段。如果没有数据，则返回空列表
	 */
	public static List<String> getIncludeFields(HttpServletRequest request){
		List<String> result = null;
		String value = request.getParameter(PublicParameter.FIELD.value());
		if(value!=null){
			if(value.matches("[0-9a-zA-Z_]+(,[0-9a-zA-Z_]+)*")){
				result = Arrays.asList(value.split(","));
			}else{
				throw new CoreException(ErrorCode.PARAMETER_ERROR,"参数"+PublicParameter.FIELD.value()+"的格式错误:"+value);
			}
		}else{
			result = new LinkedList<String>();
		}
		return result;
	}
	
	/**
	 * 从请求中获得整数类型的参数
	 * @param request HTTP请求
	 * @param parameterName 参数名
	 * @return 参数的值，如果参数不存在，则返回null
	 * @throws CoreException 如果参数的值不是整数类型，则抛出异常
	 */
	public static Integer getInt(HttpServletRequest request,String parameterName)throws CoreException{
		return getInt(request,parameterName,null);
	}
	
	/**
	 * 从请求中获得整数类型的参数
	 * @param request HTTP请求
	 * @param parameterName 参数名
	 * @param defaultValue 默认值
	 * @return 参数的值，如果参数不存在，则返回默认值
	 * @throws CoreException 如果参数的值不是整数类型，则抛出异常
	 */
	public static Integer getInt(HttpServletRequest request,String parameterName,Integer defaultValue)throws CoreException{
		Integer result = defaultValue;
		String value = request.getParameter(parameterName);
		if(value!=null){
			try{
				result = Integer.valueOf(value.trim());
			}catch(NumberFormatException e){
				throw new CoreException(ErrorCode.PARAMETER_ERROR,parameterName+"参数应该是整数:"+value);
			}
		}
		return result;
	}
	
	/**
	 * 从请求中获得整数类型的参数
	 * @param request HTTP请求
	 * @param parameterName 参数名
	 * @return 参数的值，如果参数不存在，则返回null
	 * @throws CoreException 如果参数的值不是整数类型，则抛出异常
	 */
	public static Long getLong(HttpServletRequest request,String parameterName)throws CoreException{
		return getLong(request,parameterName,null);
	}
	
	/**
	 * 从请求中获得整数类型的参数
	 * @param request HTTP请求
	 * @param parameterName 参数名
	 * @param defaultValue 默认值
	 * @return 参数的值，如果参数不存在，则返回默认值
	 * @throws CoreException 如果参数的值不是整数类型，则抛出异常
	 */
	public static Long getLong(HttpServletRequest request,String parameterName,Long defaultValue)throws CoreException{
		Long result = defaultValue;
		String value = request.getParameter(parameterName);
		if(value!=null){
			try{
				result = Long.valueOf(value.trim());
			}catch(NumberFormatException e){
				throw new CoreException(ErrorCode.PARAMETER_ERROR,parameterName+"参数应该是整数:"+value);
			}
		}
		return result;
	}
	
	/**
	 * 将HTTP请求中的通用查询参数封装为参数类
	 * @param request HTTP请求
	 * @param parameter 参数类
	 * @return 参数类的实例
	 * @see studio.xiaoyun.web.PublicParameter PublicParameter
	 */
	public static <T extends Parameter> T getParameter(HttpServletRequest request,Class<T> parameter){
		Parameter result = null;
		try{
			result = parameter.newInstance();
		}catch(Exception e){
			throw new CoreException(parameter.getName()+"实例化失败");
		}
		int start = getStart(request);
		int rows = getRows(request);
        List<String> includeFields = getIncludeFields(request);
        List<Object[]> sortFields = getSortFields(request);
        List<Criterion> criterions = getCriterions(request);
        for(Object[] objs:sortFields){
        	result.addSort((String)objs[0], (Boolean)objs[1]);
        }
        for(Criterion c:criterions){
        	result.addQuery(c);
        }
        result.addIncludeField(includeFields.toArray(new String[0]));
		result.setFirstResult(start);
		result.setMaxResults(rows);
		if(!result.validate()){
			throw new CoreException(ErrorCode.PARAMETER_ERROR,"查询条件无法解析!");
		}
		@SuppressWarnings("unchecked")
		T t = (T)result;
		return t;
	}
	
	@SuppressWarnings("rawtypes")
	private static List<Criterion> getCriterions(HttpServletRequest request){
		List<Criterion> result = new LinkedList<Criterion>();
		String value = request.getParameter(PublicParameter.QUERY.value());
		if(value!=null){
			try{
				ObjectMapper mapper = new ObjectMapper();
				Map map = mapper.readValue(value, Map.class);
				result.addAll(getCriterionForAndOr(map));
			}catch(CoreException e){
				throw e;
			}catch(Exception e){
				throw new CoreException(ErrorCode.PARAMETER_ERROR,"参数"+PublicParameter.QUERY.value()+"的值无法解析:"+value);
			}
		}
		return result;
	}
	
	@SuppressWarnings("rawtypes")
	private static List<Criterion> getCriterionForAndOr(Map map){
		List<Criterion> result = new LinkedList<Criterion>();
		Iterator it = map.keySet().iterator();
		while(it.hasNext()){
			String key = it.next().toString();
			Object val = map.get(key);
			List<Criterion> list = null;
			switch(key){
			case "_in":  //范围之中
				list = getCriterionForIn((Map)val);
				break;
			case "_between":  //范围之中
				list = getCriterionForBetween((Map)val);
				break;
			case "_gt":  //大于
				list = getCriterionForGt((Map)val);
				break;
			case "_ge":  //大于等于
				list = getCriterionForGe((Map)val);
				break;
			case "_lt":   //小于
				list = getCriterionForLt((Map)val);
				break;
			case "_le":   //小于等于
				list = getCriterionForLe((Map)val);
				break;
			case "_and": //逻辑与
				list = getCriterionForAndOr((Map)val);
				break;
			case "_or":  //逻辑或
				List<Criterion> list2 = getCriterionForAndOr((Map)val);
				list = Arrays.asList(Query.or(list2.toArray(new Criterion[0])));
				break;
			default:     //等于、模糊查询
				String text = val.toString();
				if(text.indexOf("*")>-1 || text.indexOf("?")>-1){
					text = text.replaceAll("[*]", "%").replaceAll("[?]","_");
					list = Arrays.asList(Query.like(key, text));
				}else{
					list = Arrays.asList(Query.equals(key, text));
				}
			}
			result.addAll(list);
		}
		return result;
	}
	
	@SuppressWarnings("rawtypes")
	private static List<Criterion> getCriterionForLe(Map map){
		List<Criterion> result = new LinkedList<Criterion>();
		Iterator it = map.keySet().iterator();
		while(it.hasNext()){
			String key = it.next().toString();
			Object value = map.get(key);
			result.add(Query.le(key, value));
		}
		return result;
	}
	
	@SuppressWarnings("rawtypes")
	private static List<Criterion> getCriterionForLt(Map map){
		List<Criterion> result = new LinkedList<Criterion>();
		Iterator it = map.keySet().iterator();
		while(it.hasNext()){
			String key = it.next().toString();
			Object value = map.get(key);
			result.add(Query.lt(key, value));
		}
		return result;
	}
	
	@SuppressWarnings("rawtypes")
	private static List<Criterion> getCriterionForGe(Map map){
		List<Criterion> result = new LinkedList<Criterion>();
		Iterator it = map.keySet().iterator();
		while(it.hasNext()){
			String key = it.next().toString();
			Object value = map.get(key);
			result.add(Query.ge(key, value));
		}
		return result;
	}
	
	@SuppressWarnings("rawtypes")
	private static List<Criterion> getCriterionForGt(Map map){
		List<Criterion> result = new LinkedList<Criterion>();
		Iterator it = map.keySet().iterator();
		while(it.hasNext()){
			String key = it.next().toString();
			Object value = map.get(key);
			result.add(Query.gt(key, value));
		}
		return result;
	}
	
	@SuppressWarnings("rawtypes")
	private static List<Criterion> getCriterionForBetween(Map map){
		List<Criterion> result = new LinkedList<Criterion>();
		Iterator it = map.keySet().iterator();
		while(it.hasNext()){
			String key = it.next().toString();
			List value = (List)map.get(key);
			result.add(Query.between(key, value.get(0), value.get(1)));
		}
		return result;
	}
	
	@SuppressWarnings("rawtypes")
	private static List<Criterion> getCriterionForIn(Map map){
		List<Criterion> result = new LinkedList<Criterion>();
		Iterator it = map.keySet().iterator();
		while(it.hasNext()){
			String key = it.next().toString();
			List value = (List)map.get(key);
			result.add(Query.in(key, value));
		}
		return result;
	}
	
	/**
	 * 从HTTP请求中取得{@value studio.xiaoyun.web.PublicParameter#ROWS}的值
	 * @param request HTTP请求
	 * @return 最多返回的记录的数量
	 */
	public static int getRows(HttpServletRequest request){
		int rows = getInt(request,PublicParameter.ROWS.value(),20);
		rows = rows<1||rows>1000?20:rows;
		return rows;
	}

	/**
	 * 从HTTP请求中取得{@value studio.xiaoyun.web.PublicParameter#SORT}的值。
	 * <p>输入格式为&lt;fieldName&gt; &lt;asc|desc&gt;[,&lt;fieldName&gt; &lt;asc|desc&gt;]...，
	 * 例如：“name desc,text asc”表示先以name降序排序，再以text升序排序
	 * @param request HTTP请求
	 * @return 排序参数, Object数组的第一个表示排序字段，String类型; 第二个表示是否升序，Boolean类型。如果没有数据，则返回空列表
	 */
	public static List<Object[]> getSortFields(HttpServletRequest request){
		List<Object[]> result = new LinkedList<Object[]>();
		String value = request.getParameter(PublicParameter.SORT.value());
		if(value!=null){
			if(value.matches("[0-9a-zA-Z_]+ (asc|desc)(,[0-9a-zA-Z_]+ (asc|desc))*")){
				String[] sorts = value.split(",");
				for(String sort:sorts){
					String[] s = sort.split(" ");
					Object[] o = new Object[2];
					o[0] = s[0];
					o[1] = s[1].equals("asc")?true:false;
					result.add(o);
				}
			}else{
				throw new CoreException(ErrorCode.PARAMETER_ERROR,"参数"+PublicParameter.SORT.value()+"的格式错误"+value);
			}
		}
		return result;
	}
	
	/**
	 * 从HTTP请求中取得{@value studio.xiaoyun.web.PublicParameter#START}的值。
	 * <p>参数{@value studio.xiaoyun.web.PublicParameter#PAGE}会转换为{@value studio.xiaoyun.web.PublicParameter#START}
	 * @param request HTTP请求
	 * @return 开始记录
	 */
	public static int getStart(HttpServletRequest request){
		int start = getInt(request,PublicParameter.START.value(),0);
		Integer page = getInt(request,PublicParameter.PAGE.value());
		int rows = getRows(request);
		if(page!=null){
			start = (page-1)*rows;
		}
		start = start<0?0:start;
		return start;
	}

	/**
	 * 从请求中获得String类型的参数
	 * @param request HTTP请求
	 * @param parameterName 参数名
	 * @param defaultValue 默认值
	 * @return 参数的值，如果参数不存在，则返回默认值
	 */
	public static String getString(HttpServletRequest request,String parameterName,String defaultValue){
		String value = request.getParameter(parameterName);
		if(value==null){
			return defaultValue;
		}else{
			return value;
		}
	}

}
